﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Genuilder.Extensibility;
using Genuilder.Extensions.XamlResource;
using System.Xml;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.IO;

namespace Genuilder.Extensions
{
	[DataContract]
	public class XamlResourceExtension : IExtension
	{
		class FileSet
		{
			public FileSet(string xamlFile)
			{
				_XamlFile = xamlFile;
				_CodeBehind = xamlFile + ".cs";
				_Generated = Path.GetFileNameWithoutExtension(xamlFile) + ".Resources.cs";
			}

			private readonly string _XamlFile;
			public string XamlFile
			{
				get
				{
					return _XamlFile;
				}
			}
			private readonly string _CodeBehind;
			public string CodeBehind
			{
				get
				{
					return _CodeBehind;
				}
			}
			private readonly string _Generated;
			public string Generated
			{
				get
				{
					return _Generated;
				}
			}
		}

		public XamlResourceExtension()
		{
			WarnForUnsupported = true;
		}
		[DataMember]
		public bool WarnForUnsupported
		{
			get;
			set;
		}

		[DataMember]
		public FileQuery XamlFiles
		{
			get;
			set;
		}
		#region IExtension Members

		public void Execute(ExtensionContext extensionContext)
		{
			var xamlFilesQuery = XamlFiles ?? new FileQuery().SelectInThisDirectory(true).All().ToQuery();
			var xamlItems = extensionContext.GenItems
				.GetByQuery(xamlFilesQuery)
				.Where(x => x.SourceType == SourceType.Page || x.SourceType == SourceType.ApplicationDefinition);

			foreach(var xamlItem in xamlItems)
			{
				GenerateTypedResources(xamlItem, extensionContext);
			}
		}

		private void GenerateTypedResources(GenItem xamlItem, ExtensionContext extensionContext)
		{
			FileSet fileSet = new FileSet(xamlItem.Name);
			var codeBehindItem = extensionContext.GenItems.GetByNames(fileSet.CodeBehind).FirstOrDefault();
			if(!xamlItem.Modified && (codeBehindItem == null || !codeBehindItem.Modified))
				return;
			using(var fsApp = xamlItem.Open())
			{
				var reader = new XamlResourceReader(XmlReader.Create(fsApp));
				if(reader.ResourceHolder == null)
					return;
				var resources = reader.ReadAll().ToList();
				if(resources.Count == 0)
					return;
				using(var fs = xamlItem.Children.CreateNew(fileSet.Generated).Open())
				{
					var writer = new CodeWriter(fs);
					if(codeBehindItem != null)
					{
						writer.WriteComment("----Copied namespace usings from " + fileSet.CodeBehind + "----------");
						foreach(Match match in Regex.Matches(codeBehindItem.ReadAllText(), "using ([^;]*)"))
						{
							writer.WriteUsing(match.Groups[1].Value);
						}
						writer.WriteComment("--------------");
					}
					IDisposable ns = String.IsNullOrEmpty(reader.ResourceHolder.Namespace) ? null : writer.WriteNamespace(reader.ResourceHolder.Namespace);
					writer.Write("public partial class " + reader.ResourceHolder.Name);
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						var resourceTypeName = reader.ResourceHolder.Name + "_TypedResources";
						writer.Write("public class " + resourceTypeName);
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							foreach(var resource in resources)
							{
								var resourceNs = GetCLRNamespace(resource.Type.Namespace, xamlItem.Logger, fileSet);
								var fullName = GetFullName(resourceNs, resource.Type.Name);
								writer.Write("public " + fullName + " " + resource.Key);
								writer.NewLine();
								using(writer.WriteBrackets())
								{
									writer.Write("get");
									writer.NewLine();
									using(writer.WriteBrackets())
									{
										writer.Write("return (" + fullName + ")App.Current.Resources[\"" + resource.Key + "\"];");
									}
								}
							}
						}
						writer.Write("public " + resourceTypeName + " TypedResources");
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("get");
							writer.NewLine();
							using(writer.WriteBrackets())
							{
								writer.Write("return new " + resourceTypeName + "();");
							}
						}
					}
					if(ns != null)
					{
						ns.Dispose();
					}
					writer.Flush();
				}
			}
		}

		private string GetFullName(string keyNs, string type)
		{
			if(String.IsNullOrEmpty(keyNs))
				return type;
			return String.Join(".", keyNs, type);
		}

		private string GetCLRNamespace(XamlNamespace xamlNamespace, IGenItemLogger logger, FileSet set)
		{
			if(xamlNamespace is XamlCLRNamespace)
				return ((XamlCLRNamespace)xamlNamespace).Namespace;
			else
			{
				if(WarnForUnsupported)
					logger.Warn("URI-CLR namespace mapping is not yet supported, contact me on genuilder.codeplex.com to make it implemented.\r\nAs a workaround, just add the using namespace declaration of the desired type in " + set.CodeBehind + ".\r\nTo disable this warning set XamlResourceExtension.WarnForUnsupported to false.");
				return null;
			}
		}

		#endregion
	}
}
